package piwan;

import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;

import javax.media.opengl.GL;

import piwan.GameState.Phase;
import piwan.ai.AIController;
import piwan.gui.CombatWindow;
import piwan.gui.GameGui;
import piwan.gui.HelpOverlay;
import piwan.gui.StatusBar;
import piwan.gui.WinLoseOverlay;

public class UserGame extends MainWindow implements FrameListener {
	private boolean useAi = false;
	private int aiPlayerId;
	static final boolean useAutoCaps = true;
	
	enum Mode {
		CHOOSECAPS,
		PLACE,
		ATTACK,
		MOVE,
		NOTMYTURN,
	}

	private AIController aictrl;
	
	public UserGame() {
		super("Piwan - Game");
	}
	
	public void glInited() {
		gstate = new GameState();
		try {
			loadTheme("data/themes/QtCurve/qtcurve.xml");
			// a completely unsuspicious line
			addEventHandler( new UnsuspiciousHandler() );
			gstate.loadGameRules();
			world = gstate.getWorld();
			rdWorld = new WorldRenderer(this,gstate);
			rdArrow = new ArrowRenderer(this);
			cam = new Camera(this);
			statusBar = new StatusBar(this);
			infolay = new InfoOverlay(this);
			help = new HelpOverlay(this);
			winlose = new WinLoseOverlay(this);
			regsel = new RegionSelector(this);
			regsel.addListener(infolay);
			
			modeHandlers = new ModeHandler[GameState.Phase.values().length];
			modeHandlers[GameState.Phase.CAPITALS.ordinal()] = new CapitalHandler();
			modeHandlers[GameState.Phase.PLACE.ordinal()] = new PlaceHandler();
			modeHandlers[GameState.Phase.ATTACK.ordinal()] = new AttackHandler();
			modeHandlers[GameState.Phase.MOVE.ordinal()] = new MoveHandler();
			modeHandlers[GameState.Phase.COMBAT.ordinal()] = new CombatHandler();
			
			
			aiPlayerId=-1;
			for(Player p: gstate.getPlayers()) {
				if(p.getName().equalsIgnoreCase("HAL") ) {
					aiPlayerId = p.getId();
					break;
				}
			}
		
			useAi = aiPlayerId!=-1;
			if(useAi)
				aictrl = new AIController(gstate,gstate.getPlayer(aiPlayerId));
			
			
			ctrl = new Controller();
			
			addFrameListener(this);
			
			new HotkeyHandler();
			
		} catch(Exception e) {
			die(e);
		}
		
		gl.glDepthFunc(GL.GL_LEQUAL);
		gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
		
		winlose.show("Welcome to Capital War!");
		resetTimer();
		winlose.hideAfter(3.0);
		gstate.startGame();
	}
	
	
	public void newFrame(double delta) {
		gl.glEnable(GL.GL_DEPTH_TEST);
		cam.loadMatrices();
		rdWorld.render();
		rdArrow.render();
		if(combatwin!=null)
			combatwin.render();
		statusBar.render();
		infolay.render();
		winlose.render();
		help.render();
		
	}
	
	public void updatePhase() {
		if(curhdl!=null)
			curhdl.ended();
		
		if( !notMyTurn || gstate.getPhase()==Phase.COMBAT) {
			curhdl = modeHandlers[gstate.getPhase().ordinal()];
			statusBar.setEndEnabled(false);
			statusBar.setNextEnabled(false);
			curhdl.started();
		} else {
			statusBar.feedback("Waiting for " + gstate.getCurrentPlayer().getName() + "...");
			curhdl = null;
		}
		updateStatus();
		hdlPhase = gstate.getPhase();
	}
	
	public void updateStatus() {
		String playerName = gstate.getCurrentPlayer().getName();
		String phaseName=null;
		switch(gstate.getPhase()) {
		case ATTACK: phaseName="Conquest Phase"; break;
		case MOVE: phaseName="Mobilisation Phase"; break;
		case CAPITALS: phaseName="Initial Stage"; break;
		case COMBAT: phaseName="Combat"; break;
		case PLACE: phaseName="Tactical Phase"; break;
		}
		
		
		statusBar.setStatus(playerName + " - " + phaseName);
	}
	
	public Camera getCamera() { return cam; }
	public World getWorld() { return world; }
	public WorldRenderer getWorldRenderer() { return rdWorld; }
	public GameState getGameState() { return gstate; }
	
	// State Controller
	class Controller extends GameState.Controller implements StatusBar.Listener {
		Controller() {
			gstate.super();
			for(int i=0;i<gstate.getPlayerCount();++i)
				if(i!=aiPlayerId)
					addControl(gstate.getPlayer(i));
			statusBar.setListener(this);
		}
		
		public void capitalChosen(Region r) {
			Player p = gstate.getCurrentPlayer();
			if( !isControlling(p) )
				statusBar.feedback(p.getName() + " has chosen " + r.getName() + " as one of his capitals.");
			
			infolay.update(r);
		}
		
		public void endPressed() {
			if( isInPhase(Phase.CAPITALS)  )
				((CapitalHandler)curhdl).commit();
			else if( isInPhase(Phase.COMBAT) ) {
				if(!combatEnded) {
					combatEnded=true;
					cancelCombat();
				} else {
					updatePhase();
					if(useAi)
						aictrl.continueAttacking();
				}
					
			} else
				endTurn();
				
		}
		
		public void nextPressed() {
			if( gstate.getPhase() == Phase.COMBAT )
				((CombatHandler)curhdl).nextRound();
			else
				endPhase();
		}
		
		public void soldiersPlaced(Region r, int n) {
			infolay.update(r);
		}
		
		public void soldiersRemoved(Region r, int n) {
			infolay.update(r);
		}
		
		public void phaseChanged(GameState.Phase p) {
			if(p==Phase.COMBAT) {
				combatEnded = false;
				cam.setPosition(getCombatHotspot());
			}
			if(isInPhase(Phase.COMBAT))
				combatEnded = true;
			else {
				updatePhase();
			}
		}
		public void turnChanged(Player p) {
			notMyTurn = !isControlling();
			updatePhase();
		}


		public void combatCancelled() {
			combatEnded=true;
			((CombatHandler)curhdl).cancelled();
		}


		public void combatRoundEnded(int hidden, int guessed, boolean newRound) {
			((CombatHandler)curhdl).roundEnded(hidden,guessed,newRound);
		}
		
		private boolean combatEnded = false;

		@Override
		public void playerEliminated(Player p) {
			winlose.show(p.getName() + " was eliminated!");
			winlose.hideOnInput();
		}

		@Override
		public void playerWon(Player p) {
			winlose.show(p.getName() + " won!");
		}
	}
	
	class HotkeyHandler extends EventHandler {
		HotkeyHandler() {
			addEventHandler(this);
		}
		
		public boolean onKeyPressed(KeyEvent e) {
			if( e.getKeyCode() == KeyEvent.VK_SPACE ) {
				if( statusBar.isNextEnabled() ) {
					ctrl.nextPressed();
					return true;
				}
			} else if( e.getKeyCode() == KeyEvent.VK_ENTER) {
				if( statusBar.isEndEnabled() ) {
					ctrl.endPressed();
					return true;
				}
			} else if( e.getKeyCode() == KeyEvent.VK_F1) {
				help.enable();
				return true;
			}
			return false;
		}
		
		public boolean onKeyReleased(KeyEvent e) {
			if(e.getKeyCode() == KeyEvent.VK_F1) {
				help.disable();
				return true;
			}
			return false;
		}
	}
	
	
	// Mode handlers
	interface ModeHandler  {
		void started();
		void ended();
	}
	
	// CHOOSECAPITAL
	class CapitalHandler implements RegionSelector.Listener, ModeHandler {
		public void defaultFeedback() {
			if( chosen == null)
				statusBar.feedback("Left-click on a capital territory to choose it.");
			else
				statusBar.feedback("Press 'End Turn' or <ENTER> if you're happy with your choice (" + chosen.getName() + ").");
		}
		
		public void started() {
			statusBar.setNextEnabled(false);
			statusBar.setEndEnabled(false);
			regsel.addListener(this);
			chosen = null;
			border.setColour(gstate.getCurrentPlayer().getColour());
			defaultFeedback();
			if(useAutoCaps) {
				for(Region r:world.getRegions())
					if(r.isCapital() && !r.isOwned()) {
						choose(r);
						break;
					}
				commit();
			}
			
		}
		
		public void commit() {
			ctrl.chooseCapital(chosen);
		}
		
		public void ended() {
			regsel.removeListener(this);
			chosen=null;
			border.remove();
		}
		
		public void selected( Region r, MouseEvent e ) {
			if( chosen!=r && !r.isOwned() && r.isCapital() ) {
				border.setPolygons(r.getPolygons());
				border.setColour(gstate.getCurrentPlayer().getColour());
				border.add();
				choose(r);
			} else
				statusBar.feedback("This territory is not a capital.");
		}
		
		public Colour hovered( Region r, MouseEvent e ) {
			Colour ret = null;
			if( r==null || chosen == r || !r.isCapital() )
				defaultFeedback();
			else {
				if( !r.isOwned() ) {
					statusBar.feedback("Left-click to choose " + r.getName() + ".");
					ret = RegionSelector.SELECTABLE_COLOUR;
				} else if( r.isOwnedBy(gstate.getCurrentPlayer()) )
					statusBar.feedback("You've already chosen this capital");
				else
					statusBar.feedback("This captial has been chosen by another player.");
			}
			return ret;
		}
		
		public void choose( Region r ) {
			assert(r!=null);
			if(chosen!=null)
				border.remove();
			chosen = r;
			regsel.suppressUnhover();
			border.setPolygons(chosen.getPolygons());
			border.add();
			statusBar.setEndEnabled(true);
			defaultFeedback();
		}
		
		private Region chosen = null;
		private BorderRenderer.Border border = rdWorld.getBorderRenderer().new Border();
	}
	
	class PlaceHandler implements ModeHandler, RegionSelector.Listener {
		private String soldsLeftFeed() {
			return "You have " + GameGui.ntroop(gstate.getSoldiersLeft()) + " left to place.";
		}
		
		@Override
		public void ended() {
			regsel.removeListener(this);
		}

		@Override
		public void started() {
			statusBar.setEndEnabled(false);
			statusBar.setNextEnabled(false);
			regsel.addListener(this);
			int nt = gstate.getSoldiersLeft();
			String newTroops;
			if( nt == 1 ) newTroops = "1 new troop";
			else newTroops = nt + " new troops";
			statusBar.feedback("You have " + newTroops + ". Left-click to place, right-click to remove.");
		}

		public Colour hovered(Region r, MouseEvent e) {
			if(r!=null&&gstate.canPlaceSoldiers(r, 1))
				return RegionSelector.SELECTABLE_COLOUR;
			return null;
		}

		public void selected(Region r, MouseEvent e) {
			if(e.getButton() == MouseEvent.BUTTON3) {
				if(gstate.canGetSoldiers(r, 1)) {
					ctrl.getSoldiersFrom(r, 1);
					statusBar.feedback("Troop removed. " + soldsLeftFeed() );
				}
			} else {
				if(gstate.canPlaceSoldiers(r, 1)) {
					ctrl.placeSoldiers(r, 1);
					if( gstate.getSoldiersLeft() == 0 )
						statusBar.feedback("You have no troops left to place. Press 'Next Phase' or <SPACE> to end phase.");
					else
						statusBar.feedback("Troop placed. " + soldsLeftFeed() );
				}
			}
			
			if( gstate.getSoldiersLeft() == 0 )
				statusBar.setNextEnabled(true);
			else
				statusBar.setNextEnabled(false);
		}
		
	}
	
	class MoveHandler implements ModeHandler, RegionSelector.Listener {
		private String soldsLeftFeed() {
			return "You need to place " + GameGui.ntroop(gstate.getSoldiersLeft()) + " back.";
		}

		@Override
		public void ended() {
			regsel.removeListener(this);
		}

		@Override
		public void started() {
			statusBar.setEndEnabled(true);
			statusBar.setNextEnabled(false);
			regsel.addListener(this);
			statusBar.feedback("Right-click on a territory to pick up troops.");
		}
		
		public Colour hovered(Region r, MouseEvent e) {
			if(r!=null && gstate.canPlaceSoldiers(r, 1))
				return RegionSelector.SELECTABLE_COLOUR;
			return null;
		}

		public void selected(Region r, MouseEvent e) {
			if(e.getButton()==MouseEvent.BUTTON3) {
				if(gstate.canGetSoldiers(r, 1)) {
					ctrl.getSoldiersFrom(r, 1);
					statusBar.feedback("Troop picked up. " + soldsLeftFeed());
				}
			} else {
				if(gstate.canPlaceSoldiers(r, 1)) {
					ctrl.placeSoldiers(r, 1);
					if(gstate.getSoldiersLeft()==0)
						statusBar.feedback("All troops placed. Press 'End Turn' or <ENTER> if you're done moving soldiers.");
					else
						statusBar.feedback("Troop placed. " + soldsLeftFeed());
				} else if(gstate.getSoldiersLeft()!=0)
					statusBar.feedback("You can't place troops here.");
			}
			
			if( gstate.getSoldiersLeft() == 0 )
				statusBar.setEndEnabled(true);
			else
				statusBar.setEndEnabled(false);
		}
		
	}
	
	class AttackHandler extends EventHandler implements ModeHandler, RegionSelector.Listener {
		private Region from,to;
		
		private void defaultFeedback() {
			statusBar.feedback("Drag the cursor from the region you want to attack from.");
		}
		
		@Override
		public void ended() {
			removeEventHandler(this);
			regsel.removeListener(this);
		}
		
		public boolean onMousePressed(MouseEvent e) {
			if(e.getButton() == MouseEvent.BUTTON1) {
				Region r = world.getRegionAt(cam.getWorldMouse());
				if( r != null ) {
					if( !r.isOwnedBy(gstate.getCurrentPlayer()) )
						statusBar.feedback("You can only attack from a region that you own!");
					else if( r.getSoldiers() == 1 )
						statusBar.feedback("You cannot attack with only one troop.");
					else {
						statusBar.feedback("Drag the cursor onto the territory you want to attack.");
						from = r;
						rdArrow.setFrom(from.getHotspot());
						rdArrow.setTo(cam.getWorldMouse());
						rdArrow.setColour(gstate.getCurrentPlayer().getColour());
						rdArrow.setEnabled(true);
					}
				}
				return true;
			}
			
			return false;
		}
		
		public boolean onMouseReleased(MouseEvent e) {
			if(e.getButton() == MouseEvent.BUTTON1 && from!=null) {
				Region r = world.getRegionAt(cam.getWorldMouse());
				if( r==null ) {
					statusBar.feedback("Attack cancelled.");
					from=null;
				} else if( !r.isOwned() || r.isOwnedBy(gstate.getCurrentPlayer()) ) {
					statusBar.feedback("You can only attack enemy territories.");
					from=null;
				} else if( !r.isAdjacentTo(from) ) {
					statusBar.feedback("You can only attack an adjacent territory.");
					from=null;
				} else {
					to = r;
					commit();
				}
				rdArrow.setEnabled(false);
				return true;
			}
			return false;
		}
		
		public boolean onMouseMotion(MouseEvent e) {
			if(from!=null)
				rdArrow.setTo(cam.getWorldMouse());
			return false;
		}
		
		
		public void commit() {
			ctrl.combatBegin(from, to);
		}

		@Override
		public void started() {
			statusBar.setEndEnabled(false);
			statusBar.setNextEnabled(true);
			from = null;
			to = null;
			addEventHandler(this);
			regsel.addListener(this);
			defaultFeedback();
		}

		@Override
		public Colour hovered(Region r, MouseEvent e) {
			if(r == null) return null;
			if(from == null) {
				if(gstate.canAttackFrom(r)) 
					return RegionSelector.SELECTABLE_COLOUR;
				else
					return null;
			} else if( to == null ) {
				if(gstate.canPerformAttack(from, r))
					return RegionSelector.SELECTABLE_COLOUR;
				else
					return null;
			}	
			return null;
		}

		@Override
		public void selected(Region r, MouseEvent e) {
			
			
		}
		
	}
	
	class CombatHandler extends EventHandler implements ModeHandler {
		private boolean attacked = false,defended = false;
		private boolean ctatt=false, ctdef=false;
		private boolean acceptNumbers = false;
		
		public String defaultFeedback() {
			if(ctatt && ctdef)
				return gstate.getAttacking().getName() + ", use the Numpad to enter your hidden number.";
			else if(ctatt)
				return "You're attacking, use the Numpad to enter your hidden number.";
			else if(ctdef)
				return "You're defending, use the number keys to enter your guess.";
			else
				return "Waiting for " + ctatt + " to enter his hidden number.";
		}
		
		@Override
		public void ended() {
			removeEventHandler(this);
			combatwin.remove();
			combatwin = null;
			statusBar.setCombatMode(false);
			cam.addHandler();
			regsel.enable();
			infolay.setVisible(true);
		}

		@Override
		public void started() {
			ctatt = ctrl.isControlling(gstate.getAttacking());
			ctdef = ctrl.isControlling(gstate.getDefending());
			System.out.println("Attack (" + ctatt + ") - Def (" + ctdef + ")");
			statusBar.setCombatMode(true);
			statusBar.setCloseWindow(false);
			statusBar.setNextEnabled(false);
			if(ctatt)
				statusBar.setEndEnabled(true);
			else
				statusBar.setEndEnabled(false);
			
			combatwin = new CombatWindow(UserGame.this,gstate);
			addEventHandler(this);
			cam.removeHandler();
			regsel.disable();
			
			attacked = defended = false;
			if(ctatt||ctdef)
				acceptNumbers = true;
			
			infolay.setVisible(false);
			
			statusBar.feedback( defaultFeedback() );	
		}
		
		public void roundEnded(int hidden, int guessed, boolean newRound) {
			boolean attackerWon = (hidden!=guessed);
			String plname;
			if(attackerWon)
				plname = gstate.getAttacking().getName();
			else
				plname = gstate.getDefending().getName();
			
			if((!ctatt||!ctdef) && ((ctatt && attackerWon) || (ctdef && !attackerWon)) )
				plname = "You ";
			
			statusBar.feedback(plname + " won, press 'Next Round' or <SPACE> to start the new round.");
				
			if(ctatt || ctdef) {
				statusBar.setNextEnabled(true);
				acceptNumbers = false;
			} else
				combatwin.newRound();
			combatwin.endRound(hidden, guessed, newRound);
			
			if(!newRound) {
				statusBar.setNextEnabled(false);
				statusBar.setEndEnabled(true);
				statusBar.setCloseWindow(true);
				statusBar.feedback("Combat has ended, press 'Close Window' or <ENTER> to continue.");
			}
		}
		
		public void cancelled() {
			statusBar.feedback("Combat cancelled, press 'Close Window' or <ENTER> to continue.");
			combatwin.cancelled();
			statusBar.setCloseWindow(true);
			statusBar.setEndEnabled(true);
			statusBar.setNextEnabled(false);
		}
		
		
		public void nextRound() {
			combatwin.newRound();
			statusBar.setNextEnabled(false);
			attacked = defended =false;
			if(ctatt||ctdef)
				acceptNumbers = true;
			statusBar.feedback( "New round. " + defaultFeedback() );	
		}
		
		public boolean onKeyPressed(KeyEvent e) {
			if(!acceptNumbers) {
				if(statusBar.isNextEnabled() )
					ctrl.nextPressed();
				else
					return false;
			}
			
			int val = 0;
			switch(e.getKeyCode()) {
			case KeyEvent.VK_1: val = -1; break;
			case KeyEvent.VK_2: val = -2; break;
			case KeyEvent.VK_3: val = -3; break;
			case KeyEvent.VK_4: val = -4; break;
			case KeyEvent.VK_5: val = -5; break;
			case KeyEvent.VK_6: val = -6; break;
			case KeyEvent.VK_7: val = -7; break;
			case KeyEvent.VK_8: val = -8; break;
			case KeyEvent.VK_9: val = -9; break;
			case KeyEvent.VK_NUMPAD1: val = 1; break;
			case KeyEvent.VK_NUMPAD2: val = 2; break;
			case KeyEvent.VK_NUMPAD3: val = 3; break;
			case KeyEvent.VK_NUMPAD4: val = 4; break;
			case KeyEvent.VK_NUMPAD5: val = 5; break;
			case KeyEvent.VK_NUMPAD6: val = 6; break;
			case KeyEvent.VK_NUMPAD7: val = 7; break;
			case KeyEvent.VK_NUMPAD8: val = 8; break;
			case KeyEvent.VK_NUMPAD9: val = 9; break;
			case KeyEvent.VK_MINUS: val=0; break;
			default: return false;
			}
			if( e.isControlDown() || e.isShiftDown() ) {
				if(val<0 && e.isControlDown() && e.isShiftDown())
					val = -val;
				else
					return false;
			}
			
			if( val==0 && ctatt )
				ctrl.cancelCombat();
			else if( val<0 && ctdef ) {
				
				if(!defended) {
					if( gstate.isAttackNumberValid(-val) ) {
						if(ctdef)
							statusBar.feedback("Guess entered. Waiting for " + gstate.getAttacking().getName() + " to enter his number.");
						else
							statusBar.feedback(gstate.getDefending().getName() + " entered his guess.");
						defended = true;
						ctrl.combatDefend(-val);
					} else
						statusBar.feedback("Invalid guess entered.");
				} else {
					if(ctdef)
						statusBar.feedback("You already entered your guess.");
					else
						statusBar.feedback(gstate.getDefending().getName() + " already entered his guess.");
				}
			} else if( val>0 && ctatt ) {
				if(!attacked) {
					if( gstate.isAttackNumberValid(val) ) {
						if(ctatt)
							statusBar.feedback("Hidden number entered. Waiting for " + gstate.getDefending().getName() + " to enter his guess.");
						else
							statusBar.feedback(gstate.getAttacking().getName() + " entered his hidden number.");
						attacked = true;
						ctrl.combatAttack(val);
					} else
						statusBar.feedback("Invalid number entered.");
				} else {
					if(ctatt)
						statusBar.feedback("You already entered your hidden number.");
					else
						statusBar.feedback(gstate.getAttacking().getName() + " already entered his guess.");
				}
			}
			return true;
		}
		
	}
	
	private boolean isInPhase(GameState.Phase phase) {
		return phase == hdlPhase;
	}
	
	private Vector2 getCombatHotspot() {
		Vector2 to = gstate.getCombatTo().getHotspot();
		Vector2 from = gstate.getCombatFrom().getHotspot();
		if( to.dist2(from)<=4 )
			return to.plus(from).times(.5);
		else
			return to;
	}
	
	private GameState.Phase	hdlPhase		= null;
	private ModeHandler[]	modeHandlers	= null;
	private GameState		gstate			= null;
	private Controller		ctrl			= null;
	private ModeHandler		curhdl			= null;
	private World			world			= null;
	private WorldRenderer	rdWorld			= null;
	private ArrowRenderer	rdArrow			= null;
	private RegionSelector	regsel			= null;
	private StatusBar		statusBar		= null;
	private InfoOverlay		infolay			= null;
	private Camera			cam				= null;
	private CombatWindow	combatwin 		= null;
	private HelpOverlay		help			= null;
	private WinLoseOverlay	winlose			= null;
	private boolean			notMyTurn		= false;

	
	public static void main( String[] args ) {
		new UserGame();
	}
	
	/* -------------------------------------------------------------------
	 * Ok, so you looked, are you proud of yourself now? You shouldn't be!
	 * Back to serious stuff now...
	 * -------------------------------------------------------------------
	 */
	private class UnsuspiciousHandler extends EventHandler {
		private int next = 0;
		private final int[] nothingToSeeHere = {
			KeyEvent.VK_UP,
			KeyEvent.VK_UP,
			KeyEvent.VK_DOWN,
			KeyEvent.VK_DOWN,
			KeyEvent.VK_LEFT,
			KeyEvent.VK_RIGHT,
			KeyEvent.VK_LEFT,
			KeyEvent.VK_RIGHT,
			KeyEvent.VK_B,
			KeyEvent.VK_A,
			KeyEvent.VK_ENTER,
		};
		
		public boolean onKeyReleased(KeyEvent e) {
			if(nothingToSeeHere[next] == e.getKeyCode()) ++next;
			else next = 0;
			if( next == nothingToSeeHere.length ) {
				/* Well GameState obviously needed to be an accomplice
				 * since cheating cannot be done by Controller instances.
				 */
				gstate.doAbsolutelyNothing();
				next = 0;
				
				statusBar.feedback("");
			}
			return false;
		}
	}
}
